﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Criterion;
using TestBat.IModels.Domain;
using TestBat.IModels.Factories;
using TestBat.IModels.Models;
using TestBat.IModels.Repositories;
using TestBat.Models.Attributes;
using TestBat.Models.Domain;
using TestBat.ServiceLocator;
using TestBat.Models.Extensions;

namespace TestBat.Models.Models
{
    public class ScheduleModel : Model, IScheduleModel
    {
        public ScheduleModel(IScheduleRepository scheduleRepository, ITestRepository testRepository, IScheduleFactory scheduleFactory)
        {
            this.scheduleRepository = scheduleRepository;
            this.testRepository = testRepository;
            this.scheduleFactory = scheduleFactory;
        }

        [Conversation]
        public ISchedule GetByID(Guid ID)
        {
            return scheduleRepository.GetByID(ID);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void Save(ISchedule schedule)
        {
            Validate(schedule);

            scheduleRepository.MakePersistent(schedule);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public void Delete(ISchedule schedule)
        {
            scheduleRepository.MakeTransient(schedule);
        }

        [Conversation]
        public void Revert(ISchedule schedule)
        {
            scheduleRepository.Refresh(schedule);
        }

        [Conversation(ConversationEndMode.CommitAndContinue)]
        public ISchedule Create()
        {
            ISchedule s = scheduleFactory.Create();

            scheduleRepository.MakePersistent(s);

            return s;
        }

        [Conversation]
        public IScheduleTask AddScheduleTask(ISchedule schedule, ITask task, IScheduleTask after)
        {
            // check for uniqueness
            IScheduleTask existing = (from t in schedule.Tasks where t.Task == task select t).FirstOrDefault<IScheduleTask>();
            if (existing != null)
                 return existing;

            IScheduleTask item = new ScheduleTask(); // TODO: use DI
            item.Schedule = schedule;
            item.Sort = (after == null) ? 1 : after.Sort + 1;
            item.Task = task;

            // update other item positions
            int sort = item.Sort;
            foreach(var i in schedule.Tasks)
                if (i.Sort >= sort)
                    i.Sort++;
            item.Sort--;

            scheduleRepository.MakePersistent(schedule);

            return item;
        }

        [Conversation]
        public IList<IScheduleTask> AddScheduleTasks(ISchedule schedule, IList<ITask> tasks, IScheduleTask after)
        {
            IList<IScheduleTask> items = new List<IScheduleTask>();
            foreach (var task in tasks.Reverse<ITask>())
            {
                // check for uniqueness
                IScheduleTask existing = (from t in schedule.Tasks where t.Task == task select t).FirstOrDefault<IScheduleTask>();
                if (existing != null)
                    continue;

                IScheduleTask item = new ScheduleTask(); // TODO: use DI
                item.Schedule = schedule;
                item.Sort = (after == null) ? 1 : after.Sort + 1;
                item.Task = task;

                // update other item positions
                int sort = item.Sort;
                foreach (var i in schedule.Tasks)
                    if (i.Sort >= sort)
                        i.Sort++;
                item.Sort--;

                items.Add(item);
            }

            scheduleRepository.MakePersistent(schedule);

            return items;
        }

        [Conversation]
        public void RemoveScheduleTask(ISchedule schedule, IScheduleTask item)
        {
            int sort = item.Sort;
            foreach (var i in schedule.Tasks)
                if (i.Sort >= sort)
                    i.Sort--;
            item.Sort++;

            schedule.Tasks.Remove(item); // detach item
            scheduleRepository.MakeTransient(item); // delete 
            scheduleRepository.MakePersistent(schedule); // save schedule
        }

        [Conversation]
        public void MoveScheduleTaskUp(ISchedule schedule, IScheduleTask item)
        {
            // find item before
            var before = schedule.Tasks.FirstOrDefault(i => i.Sort == item.Sort - 1);

            // swap sort
            if (before != null)
            {
                int s = before.Sort;
                before.Sort = item.Sort;
                item.Sort = s;
            }

            scheduleRepository.MakePersistent(schedule);
        }

        [Conversation]
        public void MoveScheduleTaskDown(ISchedule schedule, IScheduleTask item)
        {
            // find item after
            var after = schedule.Tasks.FirstOrDefault(i => i.Sort == item.Sort + 1);

            // swap sort
            if (after != null)
            {
                int s = after.Sort;
                after.Sort = item.Sort;
                item.Sort = s;
            }

            scheduleRepository.MakePersistent(schedule);
        }

        [Conversation]
        public IScheduleTraineeGroup AddScheduleTraineeGroup(ISchedule schedule, ITraineeGroup traineeGroup)
        {
            // check for uniqueness
            IScheduleTraineeGroup existing = (from tg in schedule.TraineeGroups where tg.Schedule.Equals(schedule) && tg.TraineeGroup.Equals(traineeGroup) select tg).FirstOrDefault<IScheduleTraineeGroup>();
            if (existing != null)
                return existing;

            IScheduleTraineeGroup scheduleTrainee = DefaultServiceLocator.Get<IScheduleTraineeGroup>();//new ScheduleTraineeGroup(); // TODO: use DI
            scheduleTrainee.TraineeGroup = traineeGroup;
            scheduleTrainee.Schedule = schedule; // attach
            scheduleRepository.MakePersistent(schedule); // save schedule

            return scheduleTrainee;
        }

        [Conversation]
        public void RemoveScheduleTraineeGroup(ISchedule schedule, IScheduleTraineeGroup traineeGroup)
        {
            schedule.TraineeGroups.Remove(traineeGroup); // detach trainee
            scheduleRepository.MakeTransient(traineeGroup); // delete
            scheduleRepository.MakePersistent(schedule); // save schedule
        }

        [Conversation]
        public IList<ISchedule> GetAll()
        {
            return scheduleRepository.GetAll();
        }

        [Conversation]
        public IList<IScheduleTask> GetScheduleTasks(ISchedule schedule)
        {
            return schedule.Tasks.ToList<IScheduleTask>(); // TODO: load the items eagerly without loading the schedule itself again (or NonUniqueExceptions occur)

            //DetachedCriteria c = DetachedCriteria.For<IScheduleItem>()
            //    //.SetFetchMode("Items", FetchMode.Eager)
            //    .Add(Expression.Eq("Schedule", schedule));
            ////var tmp = scheduleRepository.GetByCriteria<ISchedule>(c).First<ISchedule>().Items;
            //return (from t in scheduleRepository.GetByCriteria<IScheduleItem>(c) orderby t.Sort select t).ToList<IScheduleItem>();
        }

        [Conversation]
        public IList<IScheduleTraineeGroup> GetScheduleTraineeGroups(ISchedule schedule)
        {
            return schedule.TraineeGroups.ToList<IScheduleTraineeGroup>(); // TODO: load the items eagerly without loading the schedule itself again (or NonUniqueExceptions occur)
        }

        [Conversation]
        public IList<ISchedule> GetRunning()
        {
            DateTime now = DateTime.Now;
            DetachedCriteria c = DetachedCriteria.For<ISchedule>().Add(Expression.Eq("Active", true)).Add(Expression.Lt("Start", now)).Add(Expression.Gt("End", now));
            return scheduleRepository.GetByCriteria<ISchedule>(c);
        }

        [Conversation]
        public IList<ISchedule> GetRunningForTrainee(ITrainee trainee)
        {
            // TODO: use DB query instead of LINQ query for performance

            // get all
            DateTime now = DateTime.Now;
            DetachedCriteria c = DetachedCriteria.For<ISchedule>().Add(Expression.Eq("Active", true)).Add(Expression.Lt("Start", now)).Add(Expression.Gt("End", now));
            IList<ISchedule> all = scheduleRepository.GetByCriteria<ISchedule>(c);

            // filter all invalid
            IList<ISchedule> selected = (from s in all where ((from t in s.TraineeGroups where (from tg in trainee.TraineeGroups where tg == t.TraineeGroup select tg).Count<ITraineeGroup>() != 0 select t).ToList().Count != 0) select s).ToList<ISchedule>();

            // filter those, which the trainee already attended for
            selected = (from s in selected where (from t in s.Tests where t.Trainee == trainee && t.IsClosed select t).Count<ITest>() == 0 select s).ToList<ISchedule>();

            return selected;

            //string sql = "SELECT s FROM Schedule AS s WHERE s.Start < '" + now.ToString() + "' AND s.End > '" + now.ToString() + "' AND s.Active = true" +
            //" AND (SELECT count(t) FROM Test AS t WHERE t.Schedule = s AND t.Trainee.ID = '" + trainee.ID + "' AND " +
            //"(t.FinishTime != NULL OR (t.StartTime + t.Schedule.Time) < '" + now.ToString() + "')" + // IsClosed
            //") = 0";

            //IList<ISchedule> running = scheduleRepository.GetBySQL(sql);

            //return running;
        }

        [Conversation]
        public IList<ITrainee> GetTrainees(ISchedule schedule)
        {
            return (from n in schedule.TraineeGroups from l in n.TraineeGroup.Trainees select l).ToList();
        }

        [Conversation]
        public IList<ITest> GetGradedTests(ISchedule schedule)
        {
            return (from t in schedule.Tests where t.IsClosed && t.IsGraded select t).ToList<ITest>();
        }

        //[Conversation]
        //public IList<ITrainee> GetTraineesTestGraded(ISchedule schedule)
        //{
        //    return (from t in schedule.Tests where t.IsClosed && t.IsGraded select t.Trainee).ToList<ITrainee>();
        //}

        //[Conversation]
        //public IList<ITrainee> GetTraineesTestNotGraded(ISchedule schedule)
        //{
        //    //return GetTrainees(schedule).Su
        //    return EnumerableExtensions.Subtract<ITrainee>(GetTrainees(schedule), GetTraineesTestGraded(schedule)).ToList<ITrainee>();
        //}

        private readonly IScheduleRepository scheduleRepository;
        private readonly IScheduleFactory scheduleFactory;
        private readonly ITestRepository testRepository;
    }
}
